import logging

from neo4j.exceptions import ServiceUnavailable

from GlobalVar import globalVar
import os
import json
import re
import nodesemver as sv

class DependStorer:
    def __init__ (self, db_driver, batch_size):
        self.url = 'https://registry.npmjs.org/'
        self.root_path = './projects/'
        self.driver = db_driver
        self.batch_size = batch_size
        self.session = self.driver.session()
        self.tx = self.session.begin_transaction()
        self.finished_pkg_depend = []
        logging.basicConfig(filename='store_depend.log', level=logging.INFO,
                            format='%(asctime)s %(levelname)s: %(message)s')

    def db_commit(self, commit=False):
        globalVar.order_num += 1
        if commit or globalVar.order_num >= self.batch_size:
            self.tx.commit()
            globalVar.order_num = 0
            self.session.close()
            self.session = self.driver.session()
            self.tx = self.session.begin_transaction()
            globalVar.finished_pkg_depend.extend(self.finished_pkg_depend)
            os.makedirs('./finished_pkg_depend_logs', exist_ok=True)
            globalVar.save_list(globalVar.finished_pkg_depend,
                                f"./finished_pkg_depend_logs/finished_pkg_depend_{len(globalVar.finished_pkg_depend)}"
                                )
            if self.finished_pkg_depend != [] and self.finished_pkg_depend is not None:
                logging.info(f"finish stored {self.finished_pkg_depend[-1]}'s dependency")
            self.finished_pkg_depend = []

    # 向数据库中存储一个节点，一个版本，一个维护者
    def _create_owner(self, pkg_id, cur_maintainer):
        if not pkg_id or not cur_maintainer:
            print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
            print(pkg_id)
            print(cur_maintainer)
            print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")

        if isinstance(cur_maintainer, str):
            cur_maintainer = re.match(r"^([a-zA-Z\s\-]+)<(.*\@.*)>.*", cur_maintainer)
            if cur_maintainer != None:
                name = cur_maintainer.group(1)
                email = cur_maintainer.group(2)
            else:
                name = 'unknown'
                email = 'unknown'
                return None
        elif isinstance(cur_maintainer, dict):
            if 'name' not in cur_maintainer:
                # print("-%s- cannot resolve maintainer: %s"%(pkg_id, cur_maintainer))
                return None
            name = cur_maintainer['name']
            email = cur_maintainer['email'] if 'email' in cur_maintainer else 'unknown'
        else:
            name = 'unknown'
            email = 'unknown'
            return None

        query = (
            f'MERGE (p1:Pkg{{id:"{pkg_id}"}}) '
            f'MERGE (p2:Maintainer{{ name: "{name}", email:"{email}"}}) '
            'MERGE (p1)-[owned:Maintainedby]->(p2) '
            'RETURN p1,p2'
        )
        result = self.tx.run(query)
        self.db_commit()
        # try:
        #     return [{"pkg": record["p1"]["id"], 'maintainer': record["p2"]["name"] + ' ' + record["p2"]["email"]}
        #             for record in result]
        # # Capture any errors along with the query and data for traceability
        # except ServiceUnavailable as exception:
        #     logging.error("CREATE maintainer- {query} raised an error: \n {exception}".format(
        #         uery=query, exception=exception))
        #     raise

    # 向数据库中存储一个节点，一个版本，一个依赖
    def _create_dependency(self, pkg_id, dependency_id, depend_type="DependOn"):

        # dependency_id = name if version == '' else name + '-v' + version
        if not pkg_id or not dependency_id:
            print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
            print(pkg_id)
            print(dependency_id)
            print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")

        query = (
            f'MERGE (p1:Pkg{{id:"{pkg_id}"}}) '
            f'MERGE(p2:Pkg{{id:"{dependency_id}"}}) '
            f'MERGE (p1)-[depend:{depend_type}]->(p2) '
            f'RETURN p1,p2'
        )
        result = self.tx.run(query)
        self.db_commit()
        # try:
        #     return [{"pkg": record["p1"]["id"], 'dependcy': record["p2"]["id"]}
        #             for record in result]
        # # Capture any errors along with the query and data for traceability
        # except ServiceUnavailable as exception:
        #     logging.error("CREATE DEPENDENCY- {query} raised an error: \n {exception}".format(
        #         uery=query, exception=exception))
        #     raise

    def max_satisfied(self, versions, constraint):
        max_satisfied = ""
        try:
            max_satisfied = sv.max_satisfying(versions, constraint, loose=True, include_prerelease=True)
        except BaseException:
            return ""
        # dependencies中可能出现这种：
        #     tape:	"git+https://github.com/kgryte/tape.git#fix/globby"
        # satisfied_versions = []
        # for version in versions:
        #     if sv.satisfies(version, constraint, loose=True, include_prerelease=True):
        #         satisfied_versions.append(version)
        if max_satisfied is not None:
            return max_satisfied
        else:
            return ""

    def load_json(self, pkg_name):
        with open(f"./projects/" + pkg_name.replace('/','_')+ ".json", "r", encoding="utf-8") as f:
            data = json.load(f)
        return data

    def get_depend_ver(self, pkg_name, constraint):
        depend_id = pkg_name
        try:
            depend_json = self.load_json(pkg_name)
        except BaseException:
            return pkg_name
        if constraint == 'latest':
            if 'dist-tags'in depend_json.keys() and 'latest' in depend_json['dist-tags'].keys():
                return depend_json['dist-tags']['latest']
            else:
                if 'versions' in depend_json.keys():
                    # print("-%s %s- has no 'dist-tags' or 'latest' tag, using the last one!"%(self.name, self.version))
                    return list(depend_json['versions'].keys())[-1]
                else:
                    return None
        elif 'versions' in depend_json.keys():
            verinfo = list(depend_json['versions'].keys())
            try:
                select_v = sv.max_satisfying(verinfo, constraint)
            except BaseException:
                select_v = ""
            if select_v is None:
                return ""
            return select_v
        else:
            return ""


    # 存储一个节点，一个版本的全部维护者和全部依赖
    def store_depend(self, pkg_json, pkg_name, version):
        pkg_id = pkg_name
        if version == None or version == '':
            # 找不到符合约束的最大版本，或者没有'versions'字段
            version = ''
            pkg_id = pkg_name
        else:
            pkg_id = pkg_name + '@' + version

        maintainers = None
        dependency = None
        devDependencies = None

        if 'versions' in pkg_json.keys() and version in pkg_json['versions'].keys():
            if 'maintainers' in pkg_json['versions'][version].keys():
                maintainers = pkg_json['versions'][version]['maintainers']
            elif 'maintainers' in pkg_json.keys():
                maintainers = pkg_json['maintainers']
                # print("-%s %s- has no 'maintainers', use the outside 'maintainers' !"%(name, version))
            else:
                pass

            if 'dependencies' in pkg_json['versions'][version].keys():
                dependency = pkg_json['versions'][version]['dependencies']
            elif 'dependencies' in pkg_json.keys():
                dependency = pkg_json['dependencies']
                # print("-%s %s- has no 'dependencies', use the outside 'dependencies' !" % (name, version))
            else:
                pass

            # if 'devDependencies' in pkg_json['versions'][version].keys():
            #     devDependencies = pkg_json['versions'][version]['devDependencies']
            # elif 'devDependencies' in pkg_json.keys():
            #     devDependencies = pkg_json['devDependencies']
            #     # print("-%s %s- has no 'dependencies', use the outside 'dependencies' !" % (name, version))
            # else:
            #     pass
        else:
            if 'maintainers' in pkg_json.keys():
                maintainers = pkg_json['maintainers']
                # print("-%s- has no 'versions' or version -%s-, use the outside 'maintainers' !"%(name, version))
            if 'dependencies' in pkg_json.keys():
                dependency = pkg_json['dependencies']
                # print("-%s- has no 'versions' or version -%s-, use the outside 'dependencies' !" % (name, version))
            # if 'devDependencies' in pkg_json.keys():
            #     devDependencies = pkg_json['devDependencies']
            #     # print("-%s- has no 'versions' or version -%s-, use the outside 'dependencies' !" % (name, version))

        if maintainers is not None and len(maintainers) != 0:
            # 约定API中’maintainers'字段为dict的list
            for cur_maintainer in maintainers:
                # owner_rlt = session.write_transaction(self._create_owner, pkg_id, cur_maintainer)
                self._create_owner(pkg_id, cur_maintainer)

        else:
            # print("-%s- has no 'maintainers' !" % (name))
            pass


        if dependency is not None and len(dependency) != 0:
            # 约定API中’dependencies'字段为dict

            for name, version in dependency.items():
                if name in globalVar.miss_pkg:
                    dependency_id = name
                    self._create_dependency(pkg_id, dependency_id)
                else:
                    dependency_id = name+self.get_depend_ver(name, version)
                    self._create_dependency(pkg_id, dependency_id)

        else:
            self.tx.run("MERGE (p1:Pkg{id: $id}) SET p1.isleaf = $leaf_flag RETURN p1.id", id=pkg_id, leaf_flag=True)
            self.db_commit()

        # if devDependencies is not None and len(devDependencies) != 0:
        #     # 约定API中’dependencies'字段为dict
        #
        #     for name, version in devDependencies.items():
        #         if name in globalVar.miss_pkg:
        #             dependency_id = name
        #         else:
        #             dependency_id = self.get_depend_id(name, version)
        #         self._create_dependency(pkg_id, dependency_id, "DevDependOn")

    # 存储一个节点，全部版本的全部维护者和全部依赖
    def store_all_depends(self, pkg_name, pkg_json):
        if "versions" not in pkg_json:
            self.store_depend(pkg_json, pkg_name, "")
            return
        versions = pkg_json['versions']
        for ver in versions.keys():
            self.store_depend(pkg_json, pkg_name, ver)
            print(f"finish stored {pkg_name}@{ver}")

    def store_all(self, continueStoreDepend=False):
        file_list = os.listdir("./projects")
        if continueStoreDepend:
            # 排除掉已经遍历的
            finish_len = len(globalVar.finished_pkg_depend)
            file_list = file_list[finish_len:]
        for filename in file_list:
            if filename[0] == '@':
                continue
            with open("./projects/" + filename, 'r', encoding="utf-8") as f:
                pkg_json = json.load(f)
                if '_id' in pkg_json:
                    pkg_name = pkg_json['_id']
                    self.store_all_depends(pkg_name, pkg_json)
                    self.finished_pkg_depend.append(pkg_name)
                    print(f"finish stored {pkg_name}'s dependency")

                else:
                    continue
        self.db_commit(True)